#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <time.h>

#include "stop_wait.h"
#include "package.h"
#include "../dll.h"
#include "../lolev.h"

static int numseq = 1; /* numero de sequencia do pacote atual */
static int bytes_lost, msgs_lost; /* dados estatisticos*/

/* Buffer para dados */
char* buf1 = NULL;
char* buf2 = NULL;
packet_t pck1 = NULL;
packet_t pck2 = NULL;

/* Simulacao de Erros */
int static sw_error_send( char* msg, int tamanho )
{
#ifdef ERRO_BYTE
    /* 5% dos pacotes contem um byte errado */
    if( drand48() < 0.05 )
    {
        msg[tamanho-1] = msg[tamanho-1] + 1;
    }
#endif
#ifdef ERRO_MSG_PERDIDA
    /* 5% das mensagens sao perdidas */
    if ( drand48() < 0.05 )
    {
        return 1;
    }
#endif

return 0;
}

void static sw_error_delay()
{
#ifdef ERRO_DELAY
    /* 5% dos pacotes ACKs irao atrasar alem da tolerancia para recebe-los */
    if( drand48() < 0.05 )
    {
        sleep(WATCHDOGT + 1);
    }
#endif
}

/* Inicia as variaveis */
void sw_init()
{
    msgs_lost = bytes_lost = 0;
    buf1 = (char*) malloc( (lolev_mtu() + 1) * sizeof(char) );
    buf2 = (char*) malloc( (lolev_mtu() + 1) * sizeof(char) );
    pck1 = (packet_t) malloc( (lolev_mtu() + 1) * sizeof(char) );
    pck2 = (packet_t) malloc( (lolev_mtu() + 1) * sizeof(char) );
    srand48(time(NULL));
}

/* Finaliza stop and wait */
void sw_end()
{
    sw_dump_stat();
    free(buf1);
    free(buf2);
    free(pck1);
    free(pck2);
}

/* Exibe os dados estatisticos do protocolo stop and wait */
void sw_dump_stat()
{
    fprintf(stderr, "\n*********** Estatisticas do Stop And Wait ************\n\n");
    fprintf(stderr,"Mensagens perdidas: %d   (bytes perdidos %d)\n",
            msgs_lost, bytes_lost);
    fprintf(stderr, "\n******************************************************\n\n");
    return;
}

/* Testa se o pacote foi enviado de forma correta */
static int sw_pkt_ok( char *buf, int nread )
{
    if( !test_cksum(buf, nread) )
    {
        fprintf(stderr, "ERRO no checksum\n");
        return 0;
    }
    
    else if( !(get_numseq(buf) == numseq) )
    {
        fprintf(stderr, "ERRO: Mensagem fora de ordem (numseq msg %d, numseq esperado %d)\n", get_numseq(buf), numseq);
        return 0;
    }

    return 1;
}

/* Envia ack para o outro lado */
static void sw_send_ack( int p_numseq )
{
    char ack = '1';
    int nread1 = insert_header( pck1, ack, p_numseq, buf1, 0 );
    sw_error_delay(); /* Atraso de envio do ack */
    lolev_send( pck1, nread1 ); /* Envia a menssagem */
}

/* Implementa o envio seguro de mensagens */
int sw_send( int fd, int socket_endpoint )
{
    char ack = '0';
    int tamanho, nread1;
    read( fd, &tamanho, sizeof(int) );
    
    if( tamanho == FINISH )
    {
        return 0;
    }

    nread1 = read( fd, buf1, tamanho );
    assert( nread1 == tamanho );
  
    /* se algum byte tiver sido lido */
    if( nread1 > 0 )
    {
        int nread2 = insert_header( pck1, ack, numseq, buf1, nread1 );
        int i;
        fd_set watched; /* conjunto de descritores a ser usado no select */
        struct timeval timeout; /* timeout do select */

        timeout.tv_sec = WATCHDOGT; 
        timeout.tv_usec = 0;

        for( i = 1; i <= MAX_TRY; i++ )
        {
            memcpy(pck2, pck1, nread2);

            /* Simula erros */
            if( !sw_error_send(pck2, nread2) )
            {
                lolev_send( pck2, nread2);
            }

            FD_ZERO( &watched );
            if ( socket_endpoint ) FD_SET( socket_endpoint, &watched ); /* fd para camada de enlace receber dados */
            select(socket_endpoint + 1, &watched, NULL, NULL, &timeout); /* Espera confirmacao */
            
            if ( FD_ISSET(socket_endpoint, &watched) )
            {
                int nread3 = lolev_recv( buf2, lolev_mtu() );

                if( sw_pkt_ok(buf2, nread3) )
                {
                    if( !(get_ack(buf2) == '1') )
                    {
                        fprintf(stderr, "ERRO: ACK esperado\n");
                    }
                    else
                    {
                        numseq++;
                        return 1;
                    }
                }
            }
            else
            {
                fprintf(stderr, "ERRO: Time out\n");
            }
            bytes_lost += nread2;
            msgs_lost++;
            fprintf(stderr, "%da. tentativa de envio da mensagem %d\n", i, numseq);
            usleep( 100000*i*drand48() ); /* Exponential backoff */
        }
            
    }

    fprintf(stderr, "ERRO: Foram esgotadas as %d tentativas de envio da mensagem %d.\n", MAX_TRY, numseq);

    return 0;
}

/* Recebe a mensagem da rede e escreve no pipe da aplicacao */
int sw_receive(int fd)
{
    int i;

    for( i = 1; i <= MAX_TRY; i++ )
    {
        int nread1 = lolev_recv( buf2, lolev_mtu() );

        if( nread1 > 0 )
        {
            if( sw_pkt_ok(buf2, nread1) )
            {
                int nread2;
                sw_send_ack(numseq);
                numseq++;
                nread2 = erase_header( pck1, buf2, nread1 );
                write( fd, &nread2, sizeof(int) );
                write( fd, pck1, nread2 );
                return 1;
            }
            else if( test_cksum( buf2, nread1 ) && ( get_numseq(buf2) < numseq ) )
            {
                sw_send_ack( get_numseq(buf2) );

                /* Menssagem ja recebida */
                return 1;
            }

        }
        fprintf(stderr, "%da. tentativa de receber a mensagem %d\n", i, numseq);
        bytes_lost += nread1;
        msgs_lost++;
    }
    return 0;
}
